package com.heima.DIYMySpring;


import com.heima.DIYMySpring.annotaitons.Autowired;
import com.heima.DIYMySpring.annotaitons.Component;
import com.heima.DIYMySpring.annotaitons.Qualifier;
import com.heima.DIYMySpring.annotaitons.Value;
import lombok.Data;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author: Ethan Yankang
 * @Program: SpringBilibili
 * @Date: 2024-09-02 16:34
 **/

@Data
public class MyAnnotationConfigApplicationContext {
//    定义容器(缓存)
    private Map<String,Object> ioc = new HashMap<>();
//    定义存放bean的列表
    private List<String> beanNames=new ArrayList<>();

//  这里开始扫包，用于发现指定包下的类是否有注解。从而进行装配、注入等
    public MyAnnotationConfigApplicationContext(String pack) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
//        遍历包，找到目标类,返回将目标类封装成原材料后的集合。供后面生成Bean
        Set<BeanDefinition> beanDefinitions=findBeanDefintions(pack);
//        根据原材料(Class(反射)、beanName)创建Bean
        createObject(beanDefinitions);
//        自动装载机制
        autowireObject(beanDefinitions);
    }

    private void autowireObject(Set<BeanDefinition> beanDefinitions) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            Class beanClass = beanDefinition.getBeanClass();
            Field[] declaredFields = beanClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                 Autowired annotation = declaredField.getAnnotation(Autowired.class);
                if(annotation!=null){

                    //这里实现了是首先通过自定义的名字注入，不行在通过类型注入的机制！！
                    Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
                    //获取到@Autowire注解下的对应的setter方法
                    String fieldName = declaredField.getName();
                    String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);

                    //这里获取的是之前需要依赖注入的源bean对象的实例(之前通过@Component注入的)
                    Object object = ioc.get(beanDefinition.getBeanName());//获取指定的Bean名字在IOC中对应的实例("因为前面的@Component注解可能会提供别名")
                    Object obj = null;
                    if(qualifier!=null){
                        //byName
                        System.err.println("@Autowire是首先按照名字注入的，这里就是按照指定的名字注入：");
                        try {
                            String beanName = qualifier.value();
                            obj=ioc.get(beanName);
                            //为这个属性赋值
                            Method method = beanClass.getMethod(methodName, declaredField.getType());
                            //然后将取出的对应名字的Bean对象赋值给需要依赖注入的Bean对象
                            method.invoke(object, obj);
                        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //byType
                        System.err.println("@Autowire没有设定名字，那就按照依赖的类型注入:");
                    //    获取到@Autowire注解的下面的属性的类型和对应这个类型在IOC中的Bean
                        Class<?> aClasstype = declaredField.getType();
                        //先获取包名
                        Package aPackage = beanDefinition.getBeanClass().getPackage();
                        //再将类型名中的包名全替换为0,即得到了仅仅的type名(Account)！再首字母小写得到真正的bean名
                        String type = aClasstype.getName().replaceAll(aPackage.getName()+".", "");
                        type=type.substring(0,1).toLowerCase()+type.substring(1);
                        //System.out.println("type = " + type);
                    //   在通过type名在IOC里面，里面找Bean即可鸭！
                         obj = ioc.get(type);
                        //System.out.println("obj = " + obj);
                        //找到方法名
                        Method method = beanDefinition.getBeanClass().getMethod(methodName, aClasstype);
                        //找到之前通过@Component注入的源对象
                        method.invoke(object,obj);
                    }

                    ioc.put(beanDefinition.getBeanName(),object);
                }
            }
        }
    }

    /*
     * @description 这是基本属性及其包装类的注入
     * @params
     * @return
     */

    private void createObject(Set<BeanDefinition> beanDefinitions) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            //   取出beanClass
            Class beanClass = beanDefinition.getBeanClass();

            //   取出beanName，创建对象
            String beanName = beanDefinition.getBeanName();

            //创建对象
            Object obj = beanClass.getConstructor().newInstance();
            //    完成属性的赋值
            Field[] declaredFields = beanClass.getDeclaredFields();
            //如果有在字段上面赋值了，就要赋值，没有的话就直接是null
            for (Field declaredField : declaredFields) {
                //获取字段上面的注解
                Value annotation = declaredField.getAnnotation(Value.class);
                if (annotation!=null) {
                    //获取属性上的value注解的值(注意这里一定是String类型哦)
                    String value = annotation.value();
                    //获取当前属性
                    String fieldName = declaredField.getName();
                    //获取对应属性的setter方法名字
                    String methodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
                    //获取setter方法
                    Method method = beanClass.getMethod(methodName, declaredField.getType());
                    //     完成已有的可能的类型转换(因为value注解里面默认的value()是返回String类型的方法，
                    //     而注入的属性里面有Integer\Float类型的种种，所以要完成可能的属性转化，不然要出错)
                    Object val=null;
                    switch (declaredField.getType().getName()){
                        case "java.lang.Integer":
                            val=Integer.parseInt(value);
                            break;
                         case "java.lang.String":
                            val=value;
                            break;
                        case "java.lang.Float":
                            val=Float.parseFloat(value);
                            break;

                    }
                    //到这里才真正完成bean的众多属相中的某一个属性的赋值(将注解里的值设置为正确的类型后传给属性)
                    method.invoke(obj,val);
                }
            }
        //    将创建好并设置好属性的对象存入缓存
            ioc.put(beanName,obj);
        }
    }




    private Set<BeanDefinition> findBeanDefintions(String pack) {
//        1、获取指定包下的所有类级别文件
        Set<Class<?>> classes = MyToolsOfGetClass.getClasses(pack);
          //1.1、定义存放制造bean的原材料的集合
          Set<BeanDefinition> beanDefinitions=new HashSet<>();
//        2.遍历这些类，找到添加了注解的类
        for (Class<?> clazz : classes) {
            Component componentAnnotation =  clazz.getAnnotation(Component.class);
            if (componentAnnotation!=null){
            //    获取Component注解的值,这就是可以指定的BeanName
                String beanName = componentAnnotation.value();
            //    如果注解值为空，那么BeanName就等于类名首字母小写
                if ("".equals(beanName)) {
                //    获取类名(打印出来的全类名，这里仅仅需要类文件名就好了。所以使用大名鼎鼎的替换函数replaceAll()函数)
                    String className = clazz.getName().replaceAll(clazz.getPackage().getName() + ".", "");
                     beanName = className.substring(0, 1).toLowerCase() + className.substring(1);
                }
            //   将这些类封装成BeanDefinition，装载到存放制造Bean的原材料的集合中
                beanDefinitions.add(new BeanDefinition(beanName,clazz));
            //   存放bean到列表中(这里面是全部的BeanName，可以随时取出来用！！)
                beanNames.add(beanName);
            }
        }
        return beanDefinitions;
    }
}
